package com.example.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 这个是在演示自己手动实现一个阻塞队列。
// 在java里面也有已经实现的线程的阻塞队列。具体介绍见后记
public class MyselfBlockQueue {

    private List<Integer> container = new ArrayList<>();
    // we havent explicitly assigned a value to the varibal 'size' ,so it should be assingned the default value of 0
    // in actual debugging,it has been verified that the initial value of size is indeed 0
    private volatile int size; //添加volatile之后，就能保证线程之间的可见性。
    private volatile int capacity; //添加volatile之后，就能保证线程之间的可见性。

    // in the case ,there is only one lock.
    private Lock lock = new ReentrantLock(); // 可重入锁，非公平锁

    // generally, one contion associated lock is sufficent，but sometimes we need more granular level,so ,we create two asociated the condition.
    private final Condition isNull = lock.newCondition();
    private final Condition isFull = lock.newCondition();

    // 构造函数，构造的时候指定容量即可。
    MyselfBlockQueue(int capacity) {
        this.capacity = capacity;
    }

    // 往队列里面添加东西。有点类似消费者生产者模式中的produce方法。
    public void add(int data) {
        try {
            // is the lock.lock() is necessary？
            // Yes。The `lock.lock()` statement is used at the beginning of the `add()` method to acquire the lock.
            // This is necessary to ensure that the critical section of the code, which modifies the shared state (in this case, the size and container of the blocking queue), is executed atomically and is thread-safe.
            // By acquiring the lock before accessing the shared state, we prevent other threads from concurrently modifying the state and avoid potential race conditions or data inconsistencies.
            lock.lock();
            try {
                // size is current capacity
                // capacity is max capacity
                // Note that，the while loop may not execute only once.
                // while循环在每次循环开始前，首先判断条件是否成立。如果计算结果为true，就把循环体内的语句执行一遍。然后继续再回去判断条件是否已满足，如此循环......如果计算结果为false，那就直接跳到while循环的末尾，退出循环。继续往下执行while循环之外的代码。
                // at the begining ,when the code reaches this point,since the condition of the while loop is not satisfied,the while loop is skiped directly.
                while (size >= capacity) {
                    System.out.println("阻塞队列满了");
                    // it causes the current thread to wait on isFull conditon。
                    // until it is signaled or interrupted.when isFull.signal() is called on ,the conditon is warked up.
                    isFull.await();
                }
            } catch (Exception e) {
                // signal, as a verb, means (发信号)通知、表示
                isFull.signal();
                e.printStackTrace();
            }
            // at the begining, when while loop is skiped, it will reach heer directly.
            ++size;
            container.add(data);
            isNull.signal();
        } finally {
            // why there need to unlock Initiative，Because locks are meant to be released voluntarily.
            // Let's not confuse knowledge about locks with knowledge about threads.
            lock.unlock();
        }
    }

    public int take() {
        try {
            lock.lock();
            try {
                while (size == 0) {
                    System.out.println("阻塞队列空了");
                    // wait on isNull condition.
                    isNull.await();
                }
            } catch (Exception e) {
                isNull.signal();
                e.printStackTrace();
            }
            // current capacity is reduce by 1.
            --size;
            int res = container.get(0);
            // when we excute container.remove(0), the queue has one square space at least, so the
            // queue is not full any more,so we can singal the isFull conditon so that another thread can excute.
            container.remove(0);
            isFull.signal();
            return res;
        } finally {
            lock.unlock();
        }
    }


    public static void main(String[] args) {
        MyselfBlockQueue queue = new MyselfBlockQueue(5);

        // 此线程类似生产者，主要做的事情就是往队列里面添加元素。
        Thread t1 = new Thread(() -> {
            // for循环往队列里面增加100个元素
            for (int i = 0; i < 100; i++) {
                queue.add(i);
                System.out.println("塞入" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 此线程类似消费者，主要做的事情就是往队列里取出元素。
        Thread t2 = new Thread(() -> {
            // for (;;) { ... }：这是一个无限循环的语法结构，在这个循环中，程序将不断地执行其中的代码。消费一直进行下去。
            for (; ; ) {
                System.out.println("消费" + queue.take());
                // try { ... } catch (InterruptedException e) { ... }：这是一个异常处理结构，用来处理可能发生的中断异常。在这里，如果线程在阻塞时被中断，它将捕获 InterruptedException 异常，并输出异常信息。
                try {
                    //Thread.sleep(800);：这一行代码是为了模拟消费者消费一个元素之后的一些处理时间，这里暂停线程执行 800 毫秒。
                    Thread.sleep(800); // sleep会让线程进入等待状态。等待完整之后，进入可执行状态。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
    }
};


//后记：阻塞队列又是啥
// 阻塞队列是一种特殊类型的队列，具有阻塞操作的特性。它支持在队列为空时进行出队操作的线程阻塞等待，也支持在队列已满时进行入队操作的线程阻塞等待。这种特性使得阻塞队列在多线程编程中非常有用，能够很好地协调生产者和消费者线程之间的工作速率，避免了线程轮询和忙等待的不足。
//阻塞队列的主要特点包括：
//1. **线程安全性**：阻塞队列是线程安全的数据结构，可以在多线程环境中安全地进行入队和出队操作，无需额外的同步手段。
//2. **阻塞操作**：阻塞队列支持阻塞操作，当队列为空时进行出队操作的线程会被阻塞，直到队列中有元素可用；当队列已满时进行入队操作的线程会被阻塞，直到队列中有空间可用。
//3. **生产者-消费者模式**：阻塞队列是实现生产者-消费者模式的重要工具，能够很好地协调生产者和消费者线程之间的工作速率，提高系统的效率和性能。
//4. **等待和通知机制**：阻塞队列通常使用等待和通知机制来实现线程的阻塞和唤醒，以及对队列状态的监控和管理。
//java中的java.util.concurrent包中的有BlockingQueue接口。
// 此接口常见的实现类有 `ArrayBlockingQueue`、`LinkedBlockingQueue`、`PriorityBlockingQueue` 等，提供了丰富的功能和灵活的配置选项，可以满足不同场景下的需求。
// 在面试中，要求手动实现一个阻塞队列通常是为了考察面试者对多线程编程和线程同步的理解，以及对数据结构和算法的熟练程度。

// 阻塞队列有什么特点？一般适用于什么场景？
// 阻塞队列的特点是当队列为空时，消费者线程尝试从队列中取元素时会被阻塞，直到队列中有新元素；
// 当队列满时，生产者线程尝试向队列中放入元素时会被阻塞，直到队列中有空闲位置。
// 这种阻塞行为使得阻塞队列成为实现线程之间同步和协作的重要工具，能够有效地解决生产者-消费者问题中的同步与互斥、任务提交与执行之间的解耦等问题。
//
// 阻塞队列适用于多线程编程场景，特别是生产者-消费者模型。在这种模型中，生产者线程负责向队列中生产数据，而消费者线程负责从队列中消费数据。
// 阻塞队列可以作为生产者和消费者之间的通道，提供了一种线程安全的方式来实现数据的传递和同步。

// isFull.signal();i dont understand why use it
// The `isFull.signal()` statement is used to notify any waiting consumer threads that the queue is no longer full, so they can attempt to take elements from the queue. This signal is sent when an element is successfully added to the queue, potentially freeing up space for more elements.
// In a typical producer-consumer scenario, where multiple producer and consumer threads are interacting with the queue, it's essential to ensure that consumer threads are notified whenever the queue transitions from being full to having available space. This allows consumer threads to resume execution and consume elements from the queue as soon as new elements are added, preventing unnecessary blocking and ensuring efficient utilization of system resources.
// Therefore, `isFull.signal()` plays a crucial role in maintaining the correct behavior and responsiveness of the producer-consumer system by signaling waiting consumer threads when the queue becomes non-full.

